package com.fingard.dsp.bank.directbank.vbao02.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Enumeration;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//import org.apache.commons.codec.binary.Base64;

public abstract class RSAUtil {
	public static Logger logger = LoggerFactory.getLogger(RSAUtil.class);
	
	/**
	 * 获取RSA公钥对象
	 * 
	 * @param filePath  RSA公钥路径
	 * @param fileSuffix RSA公钥名称，决定编码类型
	 * @param keyAlgorithm  密钥算法
	 * @return RSA公钥对象
	 * @throws Exception
	 */
	public static PublicKey getRSAPublicKeyByFileSuffix(String filePath, String fileSuffix, String keyAlgorithm) throws Exception {
		InputStream in = null;
		if (fileSuffix == null) {
			int index=filePath.lastIndexOf(".");
			if (index > 0) {
				fileSuffix = filePath.substring(index+1);
			}
		}
		String keyType = "";
		if ("crt".equalsIgnoreCase(fileSuffix) || "txt".equalsIgnoreCase(fileSuffix) ||"cer".equalsIgnoreCase(fileSuffix)) {
			keyType = "X.509";
		} else if ("pem".equalsIgnoreCase(fileSuffix)) {
			keyType = "PKCS12";
		} else if(("yljf").equalsIgnoreCase(fileSuffix)){
			keyType = "yljf";
		} else{
			keyType = "PKCS12";
		}

		try {
			in = new FileInputStream(filePath);
			PublicKey pubKey = null;
			if ("X.509".equals(keyType)) {
				CertificateFactory factory = CertificateFactory.getInstance(keyType);
				Certificate cert = factory.generateCertificate(in);
				pubKey = cert.getPublicKey();
			} else if ("PKCS12".equals(keyType)) {
				@SuppressWarnings("resource")
				BufferedReader br = new BufferedReader(new InputStreamReader(in));
				StringBuilder sb = new StringBuilder();
				String readLine = null;
				while ((readLine = br.readLine()) != null) {
					if (readLine.charAt(0) == '-') {
						continue;
					} else {
						sb.append(readLine);
						sb.append('\r');
					}
				}
				X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64ForRSA.decode(sb.toString()));
				KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
				pubKey = keyFactory.generatePublic(pubX509);
			}else if("yljf".equals(keyType)){
				BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
				String s = br.readLine();
				@SuppressWarnings("resource")
				ASN1InputStream ain = new 	ASN1InputStream(hexString2ByteArr(s));
				RSAPublicKeyStructure pStruct = 	RSAPublicKeyStructure.getInstance(ain.readObject());
				RSAPublicKeySpec spec = new 	RSAPublicKeySpec(pStruct.getModulus(), 			pStruct.getPublicExponent());
				KeyFactory kf = KeyFactory.getInstance("RSA");
				if (in != null)
					in.close();
				return kf.generatePublic(spec);
			}

			return pubKey;
		} catch (FileNotFoundException e) {
			throw new Exception("公钥路径文件不存在");
		} catch (CertificateException e) {
			throw new Exception("生成证书文件错误");
		} catch (IOException e) {
			throw new Exception("读取公钥异常");
		} catch (NoSuchAlgorithmException e) {
			throw new Exception(String.format("生成密钥工厂时没有[%s]此类算法", keyAlgorithm));
		} catch (InvalidKeySpecException e) {
			throw new Exception("生成公钥对象异常");
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
			}
		}
	}
	
	/**
	 * 获取RSA私钥对象
	 * 
	 * @param filePath  RSA私钥路径
	 * @param fileSuffix  RSA私钥名称，决定编码类型
	 * @param password RSA私钥保护密钥
	 * @param keyAlgorithm 密钥算法
	 * @return RSA私钥对象
	 * @throws Exception
	 */
	public static PrivateKey getRSAPrivateKeyByFileSuffix(String filePath, String fileSuffix, String password, String keyAlgorithm)
			throws Exception {
		if (fileSuffix == null) {
			int index=filePath.lastIndexOf(".");
			if (index > 0) {
				fileSuffix = filePath.substring(index+1);
			}
		}
		String keyType = "";
		if ("keystore".equalsIgnoreCase(fileSuffix)) {
			keyType = "JKS";
		} else if ("pfx".equalsIgnoreCase(fileSuffix)|| "p12".equalsIgnoreCase(fileSuffix)) {
			keyType = "PKCS12";
		} else if ("jck".equalsIgnoreCase(fileSuffix)) {
			keyType = "JCEKS";
		} else if ("pem".equalsIgnoreCase(fileSuffix)|| "pkcs8".equalsIgnoreCase(fileSuffix)) {
			keyType = "PKCS8";
		} else if ("pkcs1".equalsIgnoreCase(fileSuffix)) {
			keyType = "PKCS1";
		} else if ("yljf".equalsIgnoreCase(fileSuffix)) {
			keyType = "yljf";
		} else if ("ldys".equalsIgnoreCase(fileSuffix)) {
			keyType = "ldys";
		} else {
			keyType = "JKS";
		}

		InputStream in = null;
		try {
			in = new FileInputStream(filePath);
			PrivateKey priKey = null;
			if ("JKS".equals(keyType) || "PKCS12".equals(keyType) || "JCEKS".equals(keyType)) {
				KeyStore ks = KeyStore.getInstance(keyType);
				if (password != null) {
					char[] cPasswd = password.toCharArray();
					ks.load(in, cPasswd);
					Enumeration<String> aliasenum = ks.aliases();
					String keyAlias = null;
					while (aliasenum.hasMoreElements()) {
						keyAlias = (String) aliasenum.nextElement();
						priKey = (PrivateKey) ks.getKey(keyAlias, cPasswd);
						if (priKey != null)
							break;
					}
				}
			} else if ("yljf".equals(keyType)) {
				BufferedReader br = new BufferedReader(new InputStreamReader(in));
				String s = br.readLine();
				PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(hexStrToBytes(s));
				KeyFactory keyf = KeyFactory.getInstance("RSA");
				PrivateKey myprikey = keyf.generatePrivate(priPKCS8);
				return myprikey;
			} else if ("ldys".equals(keyType)) {
				byte[] b = new byte[20480];
				in.read(b);
				PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(b);
				KeyFactory keyf = KeyFactory.getInstance("RSA");
				PrivateKey myprikey = keyf.generatePrivate(priPKCS8);
				return myprikey;
			} else {
				@SuppressWarnings("resource")
				BufferedReader br = new BufferedReader(new InputStreamReader(in));
				StringBuilder sb = new StringBuilder();
				String readLine = null;
				while ((readLine = br.readLine()) != null) {
					if (readLine.charAt(0) == '-') {
						continue;
					} else {
						sb.append(readLine);
						sb.append('\r');
					}
				}
				if ("PKCS8".equals(keyType)) {
					PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64ForRSA.decode(sb.toString()));
					KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
					priKey = keyFactory.generatePrivate(priPKCS8);
				} else if ("PKCS1".equals(keyType)) {
					RSAPrivateKeyStructure asn1PrivKey = new RSAPrivateKeyStructure(
							(ASN1Sequence) ASN1Sequence.fromByteArray(Base64ForRSA.decode(sb.toString())));
					KeySpec rsaPrivKeySpec = new RSAPrivateKeySpec(
							asn1PrivKey.getModulus(),asn1PrivKey.getPrivateExponent());
					KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
					priKey = keyFactory.generatePrivate(rsaPrivKeySpec);
				}
			}

			return priKey;
		} catch (FileNotFoundException e) {
			throw new Exception("私钥路径文件不存在");
		} catch (KeyStoreException e) {
			throw new Exception("获取KeyStore对象异常");
		} catch (IOException e) {
			throw new Exception("读取私钥异常");
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("生成私钥对象异常");
		} catch (CertificateException e) {
			throw new Exception("加载私钥密码异常");
		} catch (UnrecoverableKeyException e) {
			throw new Exception("生成私钥对象异常");
		} catch (InvalidKeySpecException e) {
			throw new Exception("生成私钥对象异常");
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
			}
		}
	}

	/**
	 * RSA加密
	 * 
	 * @param plainBytes 明文字节数组
	 * @param publicKey  公钥
	 * @param keyLength 密钥bit长度
	 * @param reserveSize padding填充字节数，预留11字节
	 * @param cipherAlgorithm 加解密算法，一般为RSA/ECB/PKCS1Padding
	 * @return 加密后字节数组，不经base64编码
	 * @throws Exception
	 */
	public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm)
			throws Exception {
		int keyByteSize = keyLength / 8; // 密钥字节数
		int encryptBlockSize = keyByteSize - reserveSize; // 加密块大小=密钥字节数-padding填充字节数
		int nBlock = plainBytes.length / encryptBlockSize;// 计算分段加密的block数，向上取整
		if ((plainBytes.length % encryptBlockSize) != 0) { // 余数非0，block数再加1
			nBlock += 1;
		}

		try {
			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);

			// 输出buffer，大小为nBlock个keyByteSize
			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
			// 分段加密
			for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
				int inputLen = plainBytes.length - offset;
				if (inputLen > encryptBlockSize) {
					inputLen = encryptBlockSize;
				}

				// 得到分段加密结果
				byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
				// 追加结果到输出buffer中
				outbuf.write(encryptedBlock);
			}

			outbuf.flush();
			outbuf.close();
			return outbuf.toByteArray();
		} catch (NoSuchAlgorithmException e) {
			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
		} catch (NoSuchPaddingException e) {
			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
		} catch (InvalidKeyException e) {
			throw new Exception("无效密钥");
		} catch (IllegalBlockSizeException e) {
			throw new Exception("加密块大小不合法");
		} catch (BadPaddingException e) {
			throw new Exception("错误填充模式");
		} catch (IOException e) {
			throw new Exception("字节输出流异常");
		}
	}

	/**
	 * RSA解密
	 * 
	 * @param encryptedBytes 加密后字节数组
	 * @param privateKey 私钥
	 * @param keyLength 密钥bit长度
	 * @param reserveSize padding填充字节数，预留11字节
	 * @param cipherAlgorithm 加解密算法，一般为RSA/ECB/PKCS1Padding
	 * @return 解密后字节数组，不经base64编码
	 * @throws Exception
	 */
	public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm)
			throws Exception {
		int keyByteSize = keyLength / 8; // 密钥字节数
		int decryptBlockSize = keyByteSize - reserveSize; // 解密块大小=密钥字节数-padding填充字节数
		int nBlock = encryptedBytes.length / keyByteSize;// 计算分段解密的block数，理论上能整除

		try {
			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);

			// 输出buffer，大小为nBlock个decryptBlockSize
			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
			// 分段解密
			for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
				// block大小: decryptBlock 或 剩余字节数
				int inputLen = encryptedBytes.length - offset;
				if (inputLen > keyByteSize) {
					inputLen = keyByteSize;
				}

				// 得到分段解密结果
				byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
				// 追加结果到输出buffer中
				outbuf.write(decryptedBlock);
			}

			outbuf.flush();
			outbuf.close();
			return outbuf.toByteArray();
		} catch (NoSuchAlgorithmException e) {
			throw new Exception(String.format("没有[%s]此类解密算法", cipherAlgorithm));
		} catch (NoSuchPaddingException e) {
			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
		} catch (InvalidKeyException e) {
			throw new Exception("无效密钥");
		} catch (IllegalBlockSizeException e) {
			throw new Exception("解密块大小不合法");
		} catch (BadPaddingException e) {
			throw new Exception("错误填充模式");
		} catch (IOException e) {
			throw new Exception("字节输出流异常");
		}
	}
	
	public static  byte[] hexString2ByteArr(String hexStr) {
		return new BigInteger(hexStr, 16).toByteArray();
	}
	
	public static final byte[] hexStrToBytes(String s) {
		byte[] bytes; 
		bytes = new byte[s.length() / 2];
		for (int i = 0; i < bytes.length; i++) { 
			bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * 		i + 2), 16);
		} 
		return bytes;
	}
	
	public static void main(String[] args) throws Exception {

		PrivateKey privateKey = RSAUtil.getRSAPrivateKeyByFileSuffix("D:\\Project\\Java\\Workspace\\dsp\\config\\portfiles\\VBAO01\\privateKey_pkcs8.pem", "pem", null, "RSA");
		PublicKey publicKey = RSAUtil.getRSAPublicKeyByFileSuffix("D:\\Project\\Java\\Workspace\\dsp\\config\\portfiles\\VBAO01\\publicKey.pem", "pem", "RSA");
		String oriStr = "{\"custBatchSeq\":\"18032829544171\",\"inputCharset\":\"UTF-8\",\"productNo\":\"A00001\",\"sign\":\"DCFEA57B1599878634700FE785D23D64\",\"version\":\"V5.1\"}";
		System.out.println("oriStrlen = "+oriStr.length());
		byte[] plainBytes = oriStr.getBytes("utf-8");
		
		byte[] encryBytes = RSAUtil.encrypt(plainBytes, publicKey, 2048, 11, "RSA/ECB/PKCS1Padding");
		String encrtptStr = new String(Base64ForRSA.encode(encryBytes).getBytes(), "UTF-8");
//		String encrtptStr = new String(Base64b.decode(new String(RSAUtil.encrypt(plainBytes, publicKey, 2048, 11, "RSA/ECB/PKCS1Padding"))));
				
		System.out.println("encrypted data:" + encrtptStr);
		
		byte[] decodeBase64KeyBytes2 = Base64ForRSA.decode(encrtptStr);
		String decodeStr = new String(RSAUtil.decrypt(decodeBase64KeyBytes2, privateKey, 2048, 11, "RSA/ECB/PKCS1Padding"),"UTF-8");
		System.out.println("decrypted data:" + decodeStr);
		
//		System.out.println("**********************************************");
	}
	
}
